ποΈGitΠ―ΡΠ°ποΈ
Node / meshtastic / Meshtastic-Android / files / feature / firmware / src / commonMain / kotlin / org / meshtastic / feature / firmware / ota / dfu / SecureDfuTransport.kt
Displaying Raw β’ Download
feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/dfu/SecureDfuTransport.kt 9c8532f80d33a3b24fab5bb7d74ea3ad9b14305f (9c8532f8) Text, 27.83 KB
T8b949e/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tf0883e@fileTb4b4b4:Te6edf3SuppressTb4b4b4(
Ta5d6ff"Ta5d6ffMagicNumberTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffThrowsCountTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffSwallowedExceptionTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72package T7ee787org.meshtastic.feature.firmware.ota.dfu
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.CompletableDeferred
Tff7b72import T7ee787kotlinx.coroutines.CoroutineDispatcher
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.Dispatchers
Tff7b72import T7ee787kotlinx.coroutines.SupervisorJob
Tff7b72import T7ee787kotlinx.coroutines.TimeoutCancellationException
Tff7b72import T7ee787kotlinx.coroutines.cancel
Tff7b72import T7ee787kotlinx.coroutines.channels.Channel
Tff7b72import T7ee787kotlinx.coroutines.delay
Tff7b72import T7ee787kotlinx.coroutines.flow.catch
Tff7b72import T7ee787kotlinx.coroutines.flow.launchIn
Tff7b72import T7ee787kotlinx.coroutines.flow.onEach
Tff7b72import T7ee787kotlinx.coroutines.withTimeout
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnectionFactory
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnectionState
Tff7b72import T7ee787org.meshtastic.core.ble.BleDevice
Tff7b72import T7ee787org.meshtastic.core.ble.BleScanner
Tff7b72import T7ee787org.meshtastic.core.ble.BleWriteType
Tff7b72import T7ee787org.meshtastic.core.ble.DEFAULT_BLE_WRITE_VALUE_LENGTH
Tff7b72import T7ee787org.meshtastic.feature.firmware.ota.calculateMacPlusOne
Tff7b72import T7ee787org.meshtastic.feature.firmware.ota.scanForBleDevice
T8b949e/**
* Kable-based transport for the Nordic Secure DFU (Secure DFU over BLE) protocol.
*
* Usage:
* 1. [triggerButtonlessDfu] β connect to the device in normal mode and trigger reboot into DFU mode.
* 2. [connectToDfuMode] β scan for the device in DFU mode and establish the DFU GATT session.
* 3. [transferInitPacket] / [transferFirmware] β send .dat then .bin.
* 4. [abort] β send ABORT to the device before closing (on cancellation or error).
* 5. [close] β tear down the connection.
*/
Tff7b72class T56d364SecureDfuTransportTb4b4b4(
Tff7b72private Tff7b72val Te6edf3scannerTb4b4b4: Te6edf3BleScannerTb4b4b4,
Te6edf3connectionFactoryTb4b4b4: Te6edf3BleConnectionFactoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3addressTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3dispatcherTb4b4b4: Te6edf3CoroutineDispatcher Tff7b72= Te6edf3DispatchersTb4b4b4.Te6edf3DefaultTb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3transportScope Tff7b72= Te6edf3CoroutineScopeTb4b4b4(Te6edf3SupervisorJobTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3dispatcherTb4b4b4)
Tff7b72private Tff7b72val Te6edf3bleConnection Tff7b72= Te6edf3connectionFactoryTb4b4b4.Te6edf3createTb4b4b4(Te6edf3transportScopeTb4b4b4, Ta5d6ff"Ta5d6ffSecure DFUTa5d6ff"Tb4b4b4)
T8b949e/** Receives binary notifications from the Control Point characteristic. */
Tff7b72private Tff7b72val Te6edf3notificationChannel Tff7b72= Te6edf3ChannelTff7b72<Te6edf3ByteArrayTff7b72>Tb4b4b4(Te6edf3ChannelTb4b4b4.Te6edf3UNLIMITEDTb4b4b4)
T8b949e// ---------------------------------------------------------------------------
T8b949e// Phase 1: Buttonless DFU trigger (normal-mode device)
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Connects to the device running normal firmware and writes to the Buttonless DFU characteristic so the bootloader
* takes over. The device disconnects and reboots.
*
* Per the Nordic Secure DFU spec, indications **must** be enabled on the Buttonless DFU characteristic before
* writing the Enter DFU command. The device validates the CCCD and rejects the write with
* `ATTERR_CPS_CCCD_CONFIG_ERROR` if indications are not enabled.
*
* After writing the trigger, the device may disconnect before the indication response arrives β this race condition
* is expected and handled gracefully.
*
* The caller must have already released the mesh-service BLE connection before calling this.
*/
Tff7b72suspend Tff7b72fun Td2a8fftriggerButtonlessDfuTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72= Te6edf3runCatching Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Scanning for device Tffd700$Te6edf3addressTa5d6ff to trigger buttonless DFU...Ta5d6ff" Tb4b4b4}
Tff7b72val Te6edf3device Tff7b72=
Te6edf3scanForDevice Tb4b4b4{ Te6edf3d Tff7b72-Tff7b72> Te6edf3dTb4b4b4.Te6edf3address Tff7b72=Tff7b72= Te6edf3address Tb4b4b4}
Tff7b72?: Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(Ta5d6ff"Ta5d6ffDevice Tffd700$Te6edf3addressTa5d6ff not found for buttonless DFU triggerTa5d6ff"Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Connecting to Tffd700$Te6edf3addressTa5d6ff to trigger buttonless DFU...Ta5d6ff" Tb4b4b4}
Te6edf3bleConnectionTb4b4b4.Te6edf3connectAndAwaitTb4b4b4(Te6edf3deviceTb4b4b4, Te6edf3CONNECT_TIMEOUT_MSTb4b4b4)
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3buttonlessChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3BUTTONLESS_NO_BONDSTb4b4b4)
T8b949e// Enable indications by subscribing to the characteristic. The device-side firmware (BLEDfuSecure.cpp)
T8b949e// checks that the CCCD is configured and returns ATTERR_CPS_CCCD_CONFIG_ERROR if not.
Tff7b72val Te6edf3indicationChannel Tff7b72= Te6edf3ChannelTff7b72<Te6edf3ByteArrayTff7b72>Tb4b4b4(Te6edf3ChannelTb4b4b4.Te6edf3UNLIMITEDTb4b4b4)
Tff7b72val Te6edf3indicationJob Tff7b72=
Te6edf3service
Tb4b4b4.Te6edf3observeTb4b4b4(Te6edf3buttonlessCharTb4b4b4)
Tb4b4b4.Te6edf3onEach Tb4b4b4{ Te6edf3indicationChannelTb4b4b4.Te6edf3trySendTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3catch Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3dTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Buttonless indication stream ended (expected on disconnect)Ta5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4.Te6edf3launchInTb4b4b4(Tff7b72thisTb4b4b4)
Te6edf3delayTb4b4b4(Te6edf3SUBSCRIPTION_SETTLE_MSTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Writing buttonless DFU trigger...Ta5d6ff" Tb4b4b4}
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3buttonlessCharTb4b4b4, Te6edf3byteArrayOfTb4b4b4(T79c0ff0Te6edf3x01Tb4b4b4)Tb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITH_RESPONSETb4b4b4)
T8b949e// Wait for the indication response (0x20-01-STATUS). The device may disconnect before we receive it β
T8b949e// that's expected and treated as success, matching the Nordic DFU library's behavior.
Tff7b72try Tb4b4b4{
Te6edf3withTimeoutTb4b4b4(Te6edf3BUTTONLESS_RESPONSE_TIMEOUT_MSTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3response Tff7b72= Te6edf3indicationChannelTb4b4b4.Te6edf3receiveTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3responseTb4b4b4.Te6edf3size Tff7b72>Tff7b72= T79c0ff3 Tff7b72&Tff7b72& Te6edf3responseTff7b72[T79c0ff0Tff7b72] Tff7b72=Tff7b72= Te6edf3BUTTONLESS_RESPONSE_CODE Tff7b72&Tff7b72& Te6edf3responseTff7b72[T79c0ff2Tff7b72] Tff7b72!Tff7b72= T79c0ff0Te6edf3x01Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Buttonless DFU response indicates error: Tffd700${Te6edf3responseTb4b4b4.Te6edf3toHexStringTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff" Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Buttonless DFU indication received successfullyTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3_Tb4b4b4: Te6edf3TimeoutCancellationExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: No buttonless indication received (device may have already disconnected)Ta5d6ff" Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3_Tb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Buttonless indication wait interrupted (device disconnecting)Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Te6edf3indicationJobTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
T8b949e// Device will disconnect and reboot β expected, not an error.
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Buttonless DFU triggered, device is rebooting...Ta5d6ff" Tb4b4b4}
Te6edf3bleConnectionTb4b4b4.Te6edf3disconnectTb4b4b4(Tb4b4b4)
Tb4b4b4}
T8b949e// ---------------------------------------------------------------------------
T8b949e// Phase 2: Connect to device in DFU mode
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Scans for the device in DFU mode (address or address+1) and establishes the GATT connection, enabling
* notifications on the Control Point.
*/
Tff7b72suspend Tff7b72fun Td2a8ffconnectToDfuModeTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72= Te6edf3runCatching Tb4b4b4{
Tff7b72val Te6edf3dfuAddress Tff7b72= Te6edf3calculateMacPlusOneTb4b4b4(Te6edf3addressTb4b4b4)
Tff7b72val Te6edf3targetAddresses Tff7b72= Te6edf3setOfTb4b4b4(Te6edf3addressTb4b4b4, Te6edf3dfuAddressTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Scanning for DFU mode device at Tffd700$Te6edf3targetAddressesTa5d6ff...Ta5d6ff" Tb4b4b4}
Tff7b72val Te6edf3device Tff7b72=
Te6edf3scanForDevice Tb4b4b4{ Te6edf3d Tff7b72-Tff7b72> Te6edf3dTb4b4b4.Te6edf3address Tff7b72in Te6edf3targetAddresses Tb4b4b4}
Tff7b72?: Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(Ta5d6ff"Ta5d6ffDFU mode device not found. Tried: Tffd700$Te6edf3targetAddressesTa5d6ff"Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Found DFU mode device at Tffd700${Te6edf3deviceTb4b4b4.Te6edf3addressTffd700}Ta5d6ff, connecting...Ta5d6ff" Tb4b4b4}
Te6edf3bleConnectionTb4b4b4.Te6edf3connectionStateTb4b4b4.Te6edf3onEach Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Connection state β Tffd700$Te6edf3itTa5d6ff" Tb4b4b4} Tb4b4b4}Tb4b4b4.Te6edf3launchInTb4b4b4(Te6edf3transportScopeTb4b4b4)
Tff7b72val Te6edf3connected Tff7b72= Te6edf3bleConnectionTb4b4b4.Te6edf3connectAndAwaitTb4b4b4(Te6edf3deviceTb4b4b4, Te6edf3CONNECT_TIMEOUT_MSTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3connected Tff7b72is Te6edf3BleConnectionStateTb4b4b4.Te6edf3DisconnectedTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ConnectionFailedTb4b4b4(Ta5d6ff"Ta5d6ffFailed to connect to DFU device Tffd700${Te6edf3deviceTb4b4b4.Te6edf3addressTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3controlChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3CONTROL_POINTTb4b4b4)
T8b949e// Subscribe to Control Point notifications before issuing any commands.
T8b949e// launchIn(this) uses connectionScope so the subscription persists beyond this block.
Tff7b72val Te6edf3subscribed Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3service
Tb4b4b4.Te6edf3observeTb4b4b4(Te6edf3controlCharTb4b4b4)
Tb4b4b4.Te6edf3onEach Tb4b4b4{ Te6edf3bytes Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3subscribedTb4b4b4.Te6edf3isCompletedTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Control Point subscribedTa5d6ff" Tb4b4b4}
Te6edf3subscribedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Tb4b4b4}
Te6edf3notificationChannelTb4b4b4.Te6edf3trySendTb4b4b4(Te6edf3bytesTb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3catch Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3subscribedTb4b4b4.Te6edf3isCompletedTb4b4b4) Te6edf3subscribedTb4b4b4.Te6edf3completeExceptionallyTb4b4b4(Te6edf3eTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Control Point notification errorTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3launchInTb4b4b4(Tff7b72thisTb4b4b4)
Te6edf3delayTb4b4b4(Te6edf3SUBSCRIPTION_SETTLE_MSTb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3subscribedTb4b4b4.Te6edf3isCompletedTb4b4b4) Te6edf3subscribedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3subscribedTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Connected and ready (Tffd700${Te6edf3deviceTb4b4b4.Te6edf3addressTffd700}Ta5d6ff)Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// ---------------------------------------------------------------------------
T8b949e// Phase 3: Init packet transfer (.dat)
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Sends the DFU init packet (`.dat` file). The device verifies this against the bootloader's security requirements
* before accepting firmware.
*
* PRN is explicitly disabled (set to 0) for the init packet per the Nordic DFU library convention β the init packet
* is small (<512 bytes, fits in a single object) and does not benefit from flow control.
*/
Tff7b72suspend Tff7b72fun Td2a8fftransferInitPacketTb4b4b4(Te6edf3initPacketTb4b4b4: Te6edf3ByteArrayTb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72= Te6edf3runCatching Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Transferring init packet (Tffd700${Te6edf3initPacketTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes)...Ta5d6ff" Tb4b4b4}
Te6edf3setPrnTb4b4b4(T79c0ff0Tb4b4b4)
Te6edf3transferObjectWithRetryTb4b4b4(Te6edf3DfuObjectTypeTb4b4b4.Te6edf3COMMANDTb4b4b4, Te6edf3initPacketTb4b4b4, Te6edf3onProgress Tff7b72= Tff7b72nullTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Init packet transferred and executed.Ta5d6ff" Tb4b4b4}
Tb4b4b4}
T8b949e// ---------------------------------------------------------------------------
T8b949e// Phase 4: Firmware transfer (.bin)
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Sends the firmware binary (`.bin` file) using the DFU object-transfer protocol.
*
* The binary is split into objects sized by the device's reported maximum object size. After each object the device
* confirms the running CRC-32. On success, the bootloader validates the full image and reboots into the new
* firmware.
*
* @param firmware Raw bytes of the `.bin` file.
* @param onProgress Callback receiving progress in [0.0, 1.0].
*/
Tff7b72suspend Tff7b72fun Td2a8fftransferFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3onProgressTb4b4b4: Te6edf3suspend Tb4b4b4(Tffa657FloatTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72> Tff7b72= Te6edf3runCatching Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Transferring firmware (Tffd700${Te6edf3firmwareTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes)...Ta5d6ff" Tb4b4b4}
Te6edf3setPrnTb4b4b4(Te6edf3PRN_INTERVALTb4b4b4)
Te6edf3transferObjectWithRetryTb4b4b4(Te6edf3DfuObjectTypeTb4b4b4.Te6edf3DATATb4b4b4, Te6edf3firmwareTb4b4b4, Te6edf3onProgressTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Firmware transferred and executed.Ta5d6ff" Tb4b4b4}
Tb4b4b4}
T8b949e// ---------------------------------------------------------------------------
T8b949e// Abort & teardown
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Sends the ABORT opcode to the device, instructing it to discard any in-progress transfer and return to an idle
* state. Best-effort β never throws.
*
* Call this before [close] when cancelling or recovering from an error so the device doesn't need a power cycle to
* accept a fresh DFU session.
*/
Tff7b72suspend Tff7b72fun Td2a8ffabortTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3runCatching Tb4b4b4{
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3controlChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3CONTROL_POINTTb4b4b4)
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3controlCharTb4b4b4, Te6edf3byteArrayOfTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3ABORTTb4b4b4)Tb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITH_RESPONSETb4b4b4)
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Abort sent to device.Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Failed to send abort (device may have disconnected)Ta5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4}
T8b949e/** Disconnect from the DFU target and cancel the transport coroutine scope. */
Tff7b72suspend Tff7b72fun Td2a8ffcloseTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3runCatching Tb4b4b4{ Te6edf3bleConnectionTb4b4b4.Te6edf3disconnectTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Error during disconnectTa5d6ff" Tb4b4b4} Tb4b4b4}
Te6edf3transportScopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
T8b949e// ---------------------------------------------------------------------------
T8b949e// Object-transfer protocol (shared by init packet and firmware)
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Wraps [transferObject] with per-object retry logic. On retry, [transferObject] will re-SELECT the object type and
* resume from the device's reported offset if the CRC matches.
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8fftransferObjectWithRetryTb4b4b4(
Te6edf3objectTypeTb4b4b4: Tffa657ByteTb4b4b4,
Te6edf3dataTb4b4b4: Te6edf3ByteArrayTb4b4b4,
Te6edf3onProgressTb4b4b4: Tb4b4b4(Te6edf3suspend Tb4b4b4(Tffa657FloatTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tff7b72?Tb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72var Te6edf3lastErrorTb4b4b4: Te6edf3Throwable? Tff7b72= Tff7b72null
Te6edf3repeatTb4b4b4(Te6edf3OBJECT_RETRY_COUNTTb4b4b4) Tb4b4b4{ Te6edf3attempt Tff7b72-Tff7b72>
Tff7b72try Tb4b4b4{
Te6edf3transferObjectTb4b4b4(Te6edf3objectTypeTb4b4b4, Te6edf3dataTb4b4b4, Te6edf3onProgressTb4b4b4)
Tff7b72return
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3e
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3ThrowableTb4b4b4) Tb4b4b4{
Te6edf3lastError Tff7b72= Te6edf3e
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Object transfer failed (attempt Tffd700${Te6edf3attempt Tff7b72+ T79c0ff1Tffd700}Ta5d6ff/Tffd700$Te6edf3OBJECT_RETRY_COUNTTa5d6ff): Tffd700${Te6edf3eTb4b4b4.Te6edf3messageTffd700}Ta5d6ff" Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3attempt Tff7b72< Te6edf3OBJECT_RETRY_COUNT Tff7b72- T79c0ff1Tb4b4b4) Te6edf3delayTb4b4b4(Te6edf3RETRY_DELAY_MSTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72throw Te6edf3lastError Tff7b72?: Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(Ta5d6ff"Ta5d6ffObject transfer failed after Tffd700$Te6edf3OBJECT_RETRY_COUNTTa5d6ff attemptsTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffLongMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffNestedBlockDepthTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8fftransferObjectTb4b4b4(Te6edf3objectTypeTb4b4b4: Tffa657ByteTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3onProgressTb4b4b4: Tb4b4b4(Te6edf3suspend Tb4b4b4(Tffa657FloatTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tff7b72?Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3selectResult Tff7b72= Te6edf3sendSelectTb4b4b4(Te6edf3objectTypeTb4b4b4)
Tff7b72val Te6edf3maxObjectSize Tff7b72= Te6edf3selectResultTb4b4b4.Te6edf3maxSizeTb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657it Tff7b72> T79c0ff0 Tb4b4b4} Tff7b72?: Te6edf3DEFAULT_MAX_OBJECT_SIZE
Tff7b72val Te6edf3totalBytes Tff7b72= Te6edf3dataTb4b4b4.Te6edf3size
Tff7b72var Te6edf3offset Tff7b72= T79c0ff0
Tff7b72var Te6edf3isFirstChunk Tff7b72= Tff7b72true
Tff7b72var Te6edf3currentPrnInterval Tff7b72= Tff7b72if Tb4b4b4(Te6edf3objectType Tff7b72=Tff7b72= Te6edf3DfuObjectTypeTb4b4b4.Te6edf3COMMANDTb4b4b4) T79c0ff0 Tff7b72else Te6edf3PRN_INTERVAL
T8b949e// Resume logic β per Nordic DFU spec, distinguish between executed objects and partial current object.
Tff7b72if Tb4b4b4(Te6edf3selectResultTb4b4b4.Te6edf3offset Tff7b72in T79c0ff1.Tb4b4b4.Te6edf3totalBytesTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3expectedCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3length Tff7b72= Te6edf3selectResultTb4b4b4.Te6edf3offsetTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3expectedCrc Tff7b72=Tff7b72= Te6edf3selectResultTb4b4b4.Te6edf3crc32Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3executedBytes Tff7b72= Te6edf3maxObjectSize Tff7b72* Tb4b4b4(Te6edf3selectResultTb4b4b4.Te6edf3offset Tff7b72/ Te6edf3maxObjectSizeTb4b4b4)
Tff7b72val Te6edf3pendingBytes Tff7b72= Te6edf3selectResultTb4b4b4.Te6edf3offset Tff7b72- Te6edf3executedBytes
Tff7b72if Tb4b4b4(Te6edf3selectResultTb4b4b4.Te6edf3offset Tff7b72=Tff7b72= Te6edf3totalBytesTb4b4b4) Tb4b4b4{
T8b949e// Device already has the complete data. Just execute.
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Device already has all Tffd700$Te6edf3totalBytesTa5d6ff bytes (CRC match), executing...Ta5d6ff" Tb4b4b4}
Te6edf3sendExecuteTb4b4b4(Tb4b4b4)
Te6edf3onProgressTff7b72?.Te6edf3invokeTb4b4b4(T79c0ff1fTb4b4b4)
Tff7b72return
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3pendingBytes Tff7b72=Tff7b72= T79c0ff0 Tff7b72&Tff7b72& Te6edf3executedBytes Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
T8b949e// Offset is at an object boundary β last complete object may not be executed yet.
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Resuming at object boundary Tffd700$Te6edf3executedBytesTa5d6ff, executing last object...Ta5d6ff" Tb4b4b4}
Tff7b72try Tb4b4b4{
Te6edf3sendExecuteTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3DfuExceptionTb4b4b4.Te6edf3ProtocolErrorTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3eTb4b4b4.Te6edf3resultCode Tff7b72!Tff7b72= Te6edf3DfuResultCodeTb4b4b4.Te6edf3OPERATION_NOT_PERMITTEDTb4b4b4) Tff7b72throw Te6edf3e
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Execute returned OPERATION_NOT_PERMITTED (already executed), continuing...Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Te6edf3offset Tff7b72= Te6edf3executedBytes
Te6edf3isFirstChunk Tff7b72= Tff7b72false
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3pendingBytes Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
T8b949e// Partial object in progress β skip to the start of the current object and resume from there.
T8b949e// We resume from the executed boundary because the partial object needs to be re-sent if we can't
T8b949e// verify the partial state cleanly. The Nordic library does the same thing.
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{
Ta5d6ff"Ta5d6ffDFU: Resuming at offset Tffd700$Te6edf3executedBytesTa5d6ff (executed=Tffd700$Te6edf3executedBytesTa5d6ff, pending=Tffd700$Te6edf3pendingBytesTa5d6ff)Ta5d6ff"
Tb4b4b4}
Te6edf3offset Tff7b72= Te6edf3executedBytes
Te6edf3isFirstChunk Tff7b72= Tff7b72false
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Offset Tffd700${Te6edf3selectResultTb4b4b4.Te6edf3offsetTffd700}Ta5d6ff CRC mismatch β restarting from 0Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72while Tb4b4b4(Te6edf3offset Tff7b72< Te6edf3totalBytesTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3objectSize Tff7b72= Te6edf3minOfTb4b4b4(Te6edf3maxObjectSizeTb4b4b4, Te6edf3totalBytes Tff7b72- Te6edf3offsetTb4b4b4)
Te6edf3sendCreateTb4b4b4(Te6edf3objectTypeTb4b4b4, Te6edf3objectSizeTb4b4b4)
T8b949e// First-chunk delay: some older bootloaders need time to prepare flash after Create.
T8b949e// The Nordic DFU library uses 400ms for the first chunk.
Tff7b72if Tb4b4b4(Te6edf3isFirstChunkTb4b4b4) Tb4b4b4{
Te6edf3delayTb4b4b4(Te6edf3FIRST_CHUNK_DELAY_MSTb4b4b4)
Te6edf3isFirstChunk Tff7b72= Tff7b72false
Tb4b4b4}
Tff7b72val Te6edf3objectEnd Tff7b72= Te6edf3offset Tff7b72+ Te6edf3objectSize
Te6edf3writePacketsTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3offsetTb4b4b4, Te6edf3objectEndTb4b4b4, Te6edf3currentPrnIntervalTb4b4b4)
Tff7b72val Te6edf3checksumResult Tff7b72= Te6edf3sendCalculateChecksumTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3expectedCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3length Tff7b72= Te6edf3objectEndTb4b4b4)
T8b949e// Bytes-lost detection: if the device reports fewer bytes than we sent, some packets were lost in
T8b949e// the BLE stack. Rather than throwing immediately, tighten PRN to 1 and retry the remaining bytes.
Tff7b72if Tb4b4b4(Te6edf3checksumResultTb4b4b4.Te6edf3offset Tff7b72< Te6edf3objectEndTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3bytesLost Tff7b72= Te6edf3objectEnd Tff7b72- Te6edf3checksumResultTb4b4b4.Te6edf3offset
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{
Ta5d6ff"Ta5d6ffDFU: Tffd700$Te6edf3bytesLostTa5d6ff bytes lost in BLE stack (sent to Tffd700$Te6edf3objectEndTa5d6ff, device at Tffd700${Te6edf3checksumResultTb4b4b4.Te6edf3offsetTffd700}Ta5d6ff)Ta5d6ff"
Tb4b4b4}
T8b949e// Verify CRC up to the device's offset is valid
Tff7b72val Te6edf3partialCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3length Tff7b72= Te6edf3checksumResultTb4b4b4.Te6edf3offsetTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3checksumResultTb4b4b4.Te6edf3crc32 Tff7b72!Tff7b72= Te6edf3partialCrcTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ChecksumMismatchTb4b4b4(Te6edf3expected Tff7b72= Te6edf3partialCrcTb4b4b4, Te6edf3actual Tff7b72= Te6edf3checksumResultTb4b4b4.Te6edf3crc32Tb4b4b4)
Tb4b4b4}
T8b949e// Tighten PRN to maximum flow control and resend the lost portion
Te6edf3currentPrnInterval Tff7b72= T79c0ff1
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Forcing PRN=1 and resending from offset Tffd700${Te6edf3checksumResultTb4b4b4.Te6edf3offsetTffd700}Ta5d6ff" Tb4b4b4}
Te6edf3writePacketsTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3checksumResultTb4b4b4.Te6edf3offsetTb4b4b4, Te6edf3objectEndTb4b4b4, Te6edf3currentPrnIntervalTb4b4b4)
Tff7b72val Te6edf3recheckResult Tff7b72= Te6edf3sendCalculateChecksumTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3recheckResultTb4b4b4.Te6edf3offset Tff7b72!Tff7b72= Te6edf3objectEnd Tff7b72|Tff7b72| Te6edf3recheckResultTb4b4b4.Te6edf3crc32 Tff7b72!Tff7b72= Te6edf3expectedCrcTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3expectedHex Tff7b72= Te6edf3expectedCrcTb4b4b4.Te6edf3toUIntTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)
Tff7b72val Te6edf3actualHex Tff7b72= Te6edf3recheckResultTb4b4b4.Te6edf3crc32Tb4b4b4.Te6edf3toUIntTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(
Ta5d6ff"Ta5d6ffRecovery failed after bytes-lost: Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffexpected offset=Tffd700$Te6edf3objectEndTa5d6ff crc=0xTffd700$Te6edf3expectedHexTa5d6ff, Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffgot offset=Tffd700${Te6edf3recheckResultTb4b4b4.Te6edf3offsetTffd700}Ta5d6ff crc=0xTffd700$Te6edf3actualHexTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Recovery successful, continuing with PRN=1Ta5d6ff" Tb4b4b4}
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3checksumResultTb4b4b4.Te6edf3offset Tff7b72!Tff7b72= Te6edf3objectEndTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(
Ta5d6ff"Ta5d6ffOffset mismatch after object: expected Tffd700$Te6edf3objectEndTa5d6ff, got Tffd700${Te6edf3checksumResultTb4b4b4.Te6edf3offsetTffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3checksumResultTb4b4b4.Te6edf3crc32 Tff7b72!Tff7b72= Te6edf3expectedCrcTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ChecksumMismatchTb4b4b4(Te6edf3expected Tff7b72= Te6edf3expectedCrcTb4b4b4, Te6edf3actual Tff7b72= Te6edf3checksumResultTb4b4b4.Te6edf3crc32Tb4b4b4)
Tb4b4b4}
T8b949e// Execute with retry for INVALID_OBJECT β the SoftDevice may still be erasing flash.
Tff7b72try Tb4b4b4{
Te6edf3sendExecuteTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3DfuExceptionTb4b4b4.Te6edf3ProtocolErrorTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3eTb4b4b4.Te6edf3resultCode Tff7b72=Tff7b72= Te6edf3DfuResultCodeTb4b4b4.Te6edf3INVALID_OBJECT Tff7b72&Tff7b72& Te6edf3offset Tff7b72+ Te6edf3objectSize Tff7b72>Tff7b72= Te6edf3totalBytesTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Execute returned INVALID_OBJECT on final object, retrying once...Ta5d6ff" Tb4b4b4}
Te6edf3delayTb4b4b4(Te6edf3RETRY_DELAY_MSTb4b4b4)
Te6edf3sendExecuteTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72throw Te6edf3e
Tb4b4b4}
Tb4b4b4}
Te6edf3offset Tff7b72= Te6edf3objectEnd
Te6edf3onProgressTff7b72?.Te6edf3invokeTb4b4b4(Te6edf3offsetTb4b4b4.Te6edf3toFloatTb4b4b4(Tb4b4b4) Tff7b72/ Te6edf3totalBytesTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Object complete. Progress: Tffd700$Te6edf3offsetTa5d6ff/Tffd700$Te6edf3totalBytesTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// ---------------------------------------------------------------------------
T8b949e// Low-level GATT helpers
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Writes [data] from [from] to [until] as MTU-sized packets WITHOUT_RESPONSE.
*
* PRN flow control: every [prnInterval] packets we await a ChecksumResult notification from the device and validate
* the running CRC-32. This prevents the device's receive buffer from overflowing and detects corruption early. Pass
* 0 to disable PRN (used for init packets).
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffwritePacketsTb4b4b4(Te6edf3dataTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3fromTb4b4b4: Tffa657IntTb4b4b4, Te6edf3untilTb4b4b4: Tffa657IntTb4b4b4, Te6edf3prnIntervalTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3mtu Tff7b72= Te6edf3bleConnectionTb4b4b4.Te6edf3maximumWriteValueLengthTb4b4b4(Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITHOUT_RESPONSETb4b4b4) Tff7b72?: Te6edf3DEFAULT_BLE_WRITE_VALUE_LENGTH
Tff7b72var Te6edf3packetsSincePrn Tff7b72= T79c0ff0
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3packetChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3PACKETTb4b4b4)
Tff7b72var Te6edf3pos Tff7b72= Te6edf3from
Tff7b72while Tb4b4b4(Te6edf3pos Tff7b72< Te6edf3untilTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3chunkEnd Tff7b72= Te6edf3minOfTb4b4b4(Te6edf3pos Tff7b72+ Te6edf3mtuTb4b4b4, Te6edf3untilTb4b4b4)
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3packetCharTb4b4b4, Te6edf3dataTb4b4b4.Te6edf3copyOfRangeTb4b4b4(Te6edf3posTb4b4b4, Te6edf3chunkEndTb4b4b4)Tb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITHOUT_RESPONSETb4b4b4)
Te6edf3pos Tff7b72= Te6edf3chunkEnd
Te6edf3packetsSincePrnTff7b72+Tff7b72+
T8b949e// Wait for the device's PRN receipt notification, then validate CRC.
T8b949e// Skip the wait on the last packet β the final CALCULATE_CHECKSUM covers it.
Tff7b72if Tb4b4b4(Te6edf3prnInterval Tff7b72> T79c0ff0 Tff7b72&Tff7b72& Te6edf3packetsSincePrn Tff7b72>Tff7b72= Te6edf3prnInterval Tff7b72&Tff7b72& Te6edf3pos Tff7b72< Te6edf3untilTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3response Tff7b72= Te6edf3awaitNotificationTb4b4b4(Te6edf3COMMAND_TIMEOUT_MSTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3response Tff7b72is Te6edf3DfuResponseTb4b4b4.Te6edf3ChecksumResultTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3expectedCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3length Tff7b72= Te6edf3posTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3responseTb4b4b4.Te6edf3offset Tff7b72!Tff7b72= Te6edf3pos Tff7b72|Tff7b72| Te6edf3responseTb4b4b4.Te6edf3crc32 Tff7b72!Tff7b72= Te6edf3expectedCrcTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ChecksumMismatchTb4b4b4(Te6edf3expected Tff7b72= Te6edf3expectedCrcTb4b4b4, Te6edf3actual Tff7b72= Te6edf3responseTb4b4b4.Te6edf3crc32Tb4b4b4)
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: PRN checksum OK at offset Tffd700$Te6edf3posTa5d6ff" Tb4b4b4}
Tb4b4b4}
Te6edf3packetsSincePrn Tff7b72= T79c0ff0
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffsendCommandTb4b4b4(Te6edf3payloadTb4b4b4: Te6edf3ByteArrayTb4b4b4)Tb4b4b4: Te6edf3DfuResponse Tb4b4b4{
Te6edf3bleConnectionTb4b4b4.Te6edf3profileTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4) Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3controlChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3CONTROL_POINTTb4b4b4)
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3controlCharTb4b4b4, Te6edf3payloadTb4b4b4, Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITH_RESPONSETb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3awaitNotificationTb4b4b4(Te6edf3COMMAND_TIMEOUT_MSTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffsetPrnTb4b4b4(Te6edf3valueTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3payload Tff7b72= Te6edf3byteArrayOfTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3SET_PRNTb4b4b4) Tff7b72+ Te6edf3intToLeBytesTb4b4b4(Te6edf3valueTb4b4b4)Tb4b4b4.Te6edf3copyOfRangeTb4b4b4(T79c0ff0Tb4b4b4, T79c0ff2Tb4b4b4)
Tff7b72val Te6edf3response Tff7b72= Te6edf3sendCommandTb4b4b4(Te6edf3payloadTb4b4b4)
Te6edf3responseTb4b4b4.Te6edf3requireSuccessTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3SET_PRNTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: PRN set to Tffd700$Te6edf3valueTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffsendSelectTb4b4b4(Te6edf3objectTypeTb4b4b4: Tffa657ByteTb4b4b4)Tb4b4b4: Te6edf3DfuResponseTb4b4b4.Te6edf3SelectResult Tb4b4b4{
Tff7b72val Te6edf3response Tff7b72= Te6edf3sendCommandTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3SELECTTb4b4b4, Te6edf3objectTypeTb4b4b4)Tb4b4b4)
Tff7b72return Tff7b72when Tb4b4b4(Te6edf3responseTb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3DfuResponseTb4b4b4.Te6edf3SelectResult Tff7b72-Tff7b72> Te6edf3response
Tff7b72is Te6edf3DfuResponseTb4b4b4.Te6edf3Failure Tff7b72-Tff7b72>
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ProtocolErrorTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3SELECTTb4b4b4, Te6edf3responseTb4b4b4.Te6edf3resultCodeTb4b4b4, Te6edf3responseTb4b4b4.Te6edf3extendedErrorTb4b4b4)
Tff7b72else Tff7b72-Tff7b72> Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(Ta5d6ff"Ta5d6ffUnexpected response to SELECT: Tffd700$Te6edf3responseTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffsendCreateTb4b4b4(Te6edf3objectTypeTb4b4b4: Tffa657ByteTb4b4b4, Te6edf3sizeTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3payload Tff7b72= Te6edf3byteArrayOfTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3CREATETb4b4b4, Te6edf3objectTypeTb4b4b4) Tff7b72+ Te6edf3intToLeBytesTb4b4b4(Te6edf3sizeTb4b4b4)
Tff7b72val Te6edf3response Tff7b72= Te6edf3sendCommandTb4b4b4(Te6edf3payloadTb4b4b4)
Te6edf3responseTb4b4b4.Te6edf3requireSuccessTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3CREATETb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Created object type=0xTffd700${Te6edf3objectTypeTb4b4b4.Te6edf3toUByteTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tffd700}Ta5d6ff size=Tffd700$Te6edf3sizeTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffsendCalculateChecksumTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3DfuResponseTb4b4b4.Te6edf3ChecksumResult Tb4b4b4{
Tff7b72val Te6edf3response Tff7b72= Te6edf3sendCommandTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3CALCULATE_CHECKSUMTb4b4b4)Tb4b4b4)
Tff7b72return Tff7b72when Tb4b4b4(Te6edf3responseTb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3DfuResponseTb4b4b4.Te6edf3ChecksumResult Tff7b72-Tff7b72> Te6edf3response
Tff7b72is Te6edf3DfuResponseTb4b4b4.Te6edf3Failure Tff7b72-Tff7b72>
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ProtocolErrorTb4b4b4(
Te6edf3DfuOpcodeTb4b4b4.Te6edf3CALCULATE_CHECKSUMTb4b4b4,
Te6edf3responseTb4b4b4.Te6edf3resultCodeTb4b4b4,
Te6edf3responseTb4b4b4.Te6edf3extendedErrorTb4b4b4,
Tb4b4b4)
Tff7b72else Tff7b72-Tff7b72> Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(Ta5d6ff"Ta5d6ffUnexpected response to CALCULATE_CHECKSUM: Tffd700$Te6edf3responseTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffsendExecuteTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3response Tff7b72= Te6edf3sendCommandTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3EXECUTETb4b4b4)Tb4b4b4)
Te6edf3responseTb4b4b4.Te6edf3requireSuccessTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3EXECUTETb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Object executed.Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffawaitNotificationTb4b4b4(Te6edf3timeoutMsTb4b4b4: Tffa657LongTb4b4b4)Tb4b4b4: Te6edf3DfuResponse Tff7b72= Tff7b72try Tb4b4b4{
Te6edf3withTimeoutTb4b4b4(Te6edf3timeoutMsTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3bytes Tff7b72= Te6edf3notificationChannelTb4b4b4.Te6edf3receiveTb4b4b4(Tb4b4b4)
Te6edf3DfuResponseTb4b4b4.Te6edf3parseTb4b4b4(Te6edf3bytesTb4b4b4)Tb4b4b4.Te6edf3also Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffDFU: Notification β Tffd700$Te6edf3itTa5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3_Tb4b4b4: Te6edf3TimeoutCancellationExceptionTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TimeoutTb4b4b4(Ta5d6ff"Ta5d6ffNo response from Control Point after Tffd700${Te6edf3timeoutMsTffd700}Ta5d6ffmsTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Te6edf3DfuResponseTb4b4b4.Td2a8ffrequireSuccessTb4b4b4(Te6edf3expectedOpcodeTb4b4b4: Tffa657ByteTb4b4b4) Tb4b4b4{
Tff7b72when Tb4b4b4(Tff7b72thisTb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3DfuResponseTb4b4b4.Te6edf3Success Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3opcode Tff7b72!Tff7b72= Te6edf3expectedOpcodeTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(
Ta5d6ff"Ta5d6ffResponse opcode mismatch: expected 0xTffd700${Te6edf3expectedOpcodeTb4b4b4.Te6edf3toUByteTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tffd700}Ta5d6ff, Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffgot 0xTffd700${Te6edf3opcodeTb4b4b4.Te6edf3toUByteTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72is Te6edf3DfuResponseTb4b4b4.Te6edf3Failure Tff7b72-Tff7b72> Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3ProtocolErrorTb4b4b4(Te6edf3opcodeTb4b4b4, Te6edf3resultCodeTb4b4b4, Te6edf3extendedErrorTb4b4b4)
Tff7b72else Tff7b72-Tff7b72>
Tff7b72throw Te6edf3DfuExceptionTb4b4b4.Te6edf3TransferFailedTb4b4b4(
Ta5d6ff"Ta5d6ffUnexpected response for opcode 0xTffd700${Te6edf3expectedOpcodeTb4b4b4.Te6edf3toUByteTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tffd700}Ta5d6ff: Tffd700$Te6edf3thisTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e// ---------------------------------------------------------------------------
T8b949e// Scanning helpers
T8b949e// ---------------------------------------------------------------------------
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffscanForDeviceTb4b4b4(Te6edf3predicateTb4b4b4: Tb4b4b4(Te6edf3BleDeviceTb4b4b4) Tff7b72-Tff7b72> Tffa657BooleanTb4b4b4)Tb4b4b4: Te6edf3BleDevice? Tff7b72= Te6edf3scanForBleDeviceTb4b4b4(
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffDFUTa5d6ff"Tb4b4b4,
Te6edf3serviceUuid Tff7b72= Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3SERVICETb4b4b4,
Te6edf3retryCount Tff7b72= Te6edf3SCAN_RETRY_COUNTTb4b4b4,
Te6edf3retryDelayMs Tff7b72= Te6edf3SCAN_RETRY_DELAY_MSTb4b4b4,
Te6edf3predicate Tff7b72= Te6edf3predicateTb4b4b4,
Tb4b4b4)
T8b949e// ---------------------------------------------------------------------------
T8b949e// Constants
T8b949e// ---------------------------------------------------------------------------
Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72const Tff7b72val Te6edf3CONNECT_TIMEOUT_MS Tff7b72= T79c0ff1T79c0ff5Te6edf3_000L
Tff7b72private Tff7b72const Tff7b72val Te6edf3COMMAND_TIMEOUT_MS Tff7b72= T79c0ff3T79c0ff0Te6edf3_000L
Tff7b72private Tff7b72const Tff7b72val Te6edf3SUBSCRIPTION_SETTLE_MS Tff7b72= T79c0ff5T79c0ff0T79c0ff0L
Tff7b72private Tff7b72const Tff7b72val Te6edf3BUTTONLESS_RESPONSE_TIMEOUT_MS Tff7b72= T79c0ff3Te6edf3_000L
Tff7b72private Tff7b72const Tff7b72val Te6edf3SCAN_RETRY_COUNT Tff7b72= T79c0ff3
Tff7b72private Tff7b72const Tff7b72val Te6edf3SCAN_RETRY_DELAY_MS Tff7b72= T79c0ff2Te6edf3_000L
Tff7b72private Tff7b72const Tff7b72val Te6edf3RETRY_DELAY_MS Tff7b72= T79c0ff2Te6edf3_000L
Tff7b72private Tff7b72const Tff7b72val Te6edf3FIRST_CHUNK_DELAY_MS Tff7b72= T79c0ff4T79c0ff0T79c0ff0L
T8b949e/** Response code prefix for Buttonless DFU indications (0x20 = response). */
Tff7b72private Tff7b72const Tff7b72val Te6edf3BUTTONLESS_RESPONSE_CODETb4b4b4: Tffa657Byte Tff7b72= T79c0ff0Te6edf3x20
T8b949e/**
* PRN interval: device sends a ChecksumResult notification every N packets. Provides flow control and early CRC
* validation. 0 = disabled.
*/
Tff7b72private Tff7b72const Tff7b72val Te6edf3PRN_INTERVAL Tff7b72= T79c0ff1T79c0ff0
T8b949e/** Number of times to retry a failed object transfer before giving up. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3OBJECT_RETRY_COUNT Tff7b72= T79c0ff3
Tff7b72private Tff7b72const Tff7b72val Te6edf3DEFAULT_MAX_OBJECT_SIZE Tff7b72= T79c0ff4T79c0ff0T79c0ff9T79c0ff6
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.4 - Generated in 0.08s